// 蜂窝蜜造平台生成代码，如手工更改，请添加到 .beeignore 忽略生成

    package com.fowo.api.service.impl;
    
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.map.MapUtil;
    import com.alibaba.excel.EasyExcel;
    import com.alibaba.excel.context.AnalysisContext;
    import com.alibaba.excel.read.listener.ReadListener;
    
    import java.util.Date;
    import java.util.List;
    
    import java.util.ArrayList;
    import java.util.Set;
    
    import com.fowo.api.common.util.ResponseUtils;
    
    import org.springframework.stereotype.Service;
    import org.springframework.beans.BeanUtils;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import org.springframework.transaction.annotation.Transactional;
    
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    
    
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.web.multipart.MultipartFile;
    import org.springframework.util.StringUtils;
    
    import javax.validation.Validator;
    import javax.validation.ConstraintViolation;
    import javax.validation.ConstraintViolationException;
    
    import lombok.extern.slf4j.Slf4j;

    
import com.fowo.api.user.model.JwtUserInfo;
import com.fowo.api.entity.InventoryPlacementR;
import com.fowo.api.model.inventory.placement.r.InventoryPlacementRVo;
import com.fowo.api.model.inventory.placement.r.InventoryPlacementRItemVo;
import com.fowo.api.model.inventory.placement.r.InventoryPlacementRSearchParamPo;

import com.fowo.api.mapper.InventoryPlacementRMapper;
    import com.fowo.api.service.InventoryPlacementRService;
    
    import javax.annotation.Resource;
import java.util.Map;
import java.util.HashMap;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.fowo.api.common.excel.ExcelConfig;
import com.fowo.api.sys.entity.SysFile;
import com.fowo.api.model.inventory.placement.r.InventoryPlacementRImportPo;
import com.fowo.api.common.model.ImportRow;
import com.fowo.api.common.util.SheetUtils;
import com.fowo.api.common.model.JoinModel;
import com.fowo.api.mapper.ShopInfoMapper;
import com.fowo.api.model.shop.info.ShopInfoSearchParamPo;
import com.fowo.api.model.shop.info.ShopInfoItemVo;
import com.fowo.api.mapper.ProductMapper;
import com.fowo.api.model.product.ProductSearchParamPo;
import com.fowo.api.model.product.ProductItemVo;
import java.io.InputStream;
import com.fowo.api.sys.service.FileService;
import com.fowo.api.sys.service.SysImportTemplateService;
import com.fowo.api.sys.entity.SysImportTemplate;
import com.fowo.api.common.excel.TemplateModelBuildEventListener;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.fowo.api.common.model.RException;
import com.fowo.api.model.inventory.placement.r.InventoryPlacementRExcelPo;
import com.fowo.api.common.excel.CustomExcelExport;
import com.fowo.api.common.util.DateUtils;
import com.fowo.api.common.excel.CustomExcelHandler;
import java.util.stream.Collectors;
import java.util.Optional;
import com.fowo.api.common.model.OptionItem;
import org.springframework.cache.annotation.Cacheable;
import com.fowo.api.common.config.CacheConfig;
import com.fowo.api.flow.service.WorkFlowService;
import com.fowo.api.flow.entity.WorkFlow;
import org.apache.commons.collections4.CollectionUtils;
import java.util.Objects;
import java.util.Collections;

    /**
        * 库存货位放置表 服务实现类
    * @author 蜂窝蜜造平台 配置人：蜂窝软件，王坤
        */
@Service
@Slf4j
   @DS("ds24")
   public class InventoryPlacementRServiceImpl extends ServiceImpl<InventoryPlacementRMapper, InventoryPlacementR> implements InventoryPlacementRService {
   
/** 引用字段排序字段名转换信息 */
public static final Map<String, String> SORT_FIELD_MAP = new HashMap<>(){{
        put("shopIdShopName", "si.`shop_name`");
put("itemIdName", "p.`name`");
    }};
// 导出时单次查询最大记录数
protected static final int EXPORT_QUERY_MAX_SIZE = 1000;
// 支持导出的最大记录数(1048576 为 Excel 支持的最大行数)
protected static final long EXPORT_MAX_SIZE = 1048575;
@Resource
                protected ShopInfoMapper shopInfoMapper;
@Resource
                protected ProductMapper productMapper;
@Resource
    protected FileService fileService;
@Resource
            protected SysImportTemplateService importTemplateService;
@Resource
    private CustomExcelExport customExcelExport;
@Resource
private WorkFlowService workFlowService;

    @Resource 
    protected Validator validator;
    /**
    * 创建库存货位放置表
    * @param model 库存货位放置表
    * @return 新数据的主键
    */
@Transactional(rollbackFor = Exception.class)
@Override
public Long create(InventoryPlacementRVo model) throws Exception {
JwtUserInfo currentUser = JwtUserInfo.fromHeader();
InventoryPlacementR entity = new InventoryPlacementR();
    BeanUtils.copyProperties(model, entity);
entity.setCreateTime(new Date());
entity.setLastUpdateTime(new Date());
if (currentUser != null) {
        entity.setCreateUser(currentUser.getUserId());
        entity.setLastUpdateUser(currentUser.getUserId());
    }
if(this.save(entity)) {
return entity.getId();
}
throw new Exception("库存货位放置表保存失败");
}
/**
    * 更新库存货位放置表
    * @param model 库存货位放置表
    * @return 更新是否成功
    */
@Override
@Transactional(rollbackFor = Exception.class)
   public boolean update(InventoryPlacementRVo model) throws Exception {
JwtUserInfo currentUser = JwtUserInfo.fromHeader();
InventoryPlacementR entity = this.getById(model.getId());
    if(entity == null) {
        throw new Exception("库存货位放置表不存在");
    }
BeanUtils.copyProperties(model, entity, "id", "lastUpdateUser", "lastUpdateTime", "createUser", "createTime");
entity.setLastUpdateTime(new Date());
if (currentUser != null) {
       entity.setLastUpdateUser(currentUser.getUserId());
   }
if (this.updateById(entity)) {
return true;
        }
        return false;
    }/**
    * 更新库存货位放置表（带空值）
    * @param model 库存货位放置表
    * @return 更新是否成功
    */
@Override
@Transactional(rollbackFor = Exception.class)
   public boolean updateForEdit(InventoryPlacementRVo model) throws Exception {
JwtUserInfo currentUser = JwtUserInfo.fromHeader();
InventoryPlacementR entity = this.getById(model.getId());
    if(entity == null) {
        throw new Exception("库存货位放置表不存在");
    }
model.setLastUpdateTime(new Date());
if (currentUser != null) {
    model.setLastUpdateUser(currentUser.getUserId());
  }
boolean isOk = new LambdaUpdateChainWrapper<>(baseMapper)
.set(InventoryPlacementR::getShelfLife, model.getShelfLife())
.set(InventoryPlacementR::getWayQty, model.getWayQty())
.set(InventoryPlacementR::getLockQty, model.getLockQty())
.set(InventoryPlacementR::getFnSku, model.getFnSku())
.set(InventoryPlacementR::getShopId, model.getShopId())
.set(InventoryPlacementR::getVersion, model.getVersion())
.set(InventoryPlacementR::getAuxQty, model.getAuxQty())
.set(InventoryPlacementR::getAuxId, model.getAuxId())
.set(InventoryPlacementR::getStockAmount, model.getStockAmount())
.set(InventoryPlacementR::getStockQty, model.getStockQty())
.set(InventoryPlacementR::getExpiryDate, model.getExpiryDate())
.set(InventoryPlacementR::getProductionDate, model.getProductionDate())
.set(InventoryPlacementR::getBatchNo, model.getBatchNo())
.set(InventoryPlacementR::getItemId, model.getItemId())
.set(InventoryPlacementR::getSku, model.getSku())
.set(InventoryPlacementR::getPicUrl, model.getPicUrl())
.set(InventoryPlacementR::getLastUpdateUser, model.getLastUpdateUser())
.set(InventoryPlacementR::getLastUpdateTime, model.getLastUpdateTime())
.set(InventoryPlacementR::getProjectId, model.getProjectId())
.set(InventoryPlacementR::getBinId, model.getBinId())
.set(InventoryPlacementR::getStockId, model.getStockId())
.eq(InventoryPlacementR::getId, model.getId())
.update();
if (isOk) {
}
return isOk;
}

    /**
     * 是否走导出中心
     */
    @Override  
    public boolean getBackgroundExportFlag(InventoryPlacementRSearchParamPo search) { 
      return false ;
    }
    
/**
    * 删除库存货位放置表
    * @param id 库存货位放置表的主键
    * @return 删除是否成功
    */
@Override
public boolean delete(Long id) throws Exception {
   return this.removeById(id);
   }
/**
    * 批量删除库存货位放置表
    * @param ids 库存货位放置表的主键列表
    * @return 删除是否成功
    */
@Override
public boolean batchDelete(List<Long> ids) throws Exception {
    return baseMapper.deleteBatchIds(ids) > 0;
   }
/**
    * 导入单行处理
    * @param row 要导入的行
    * @param rowNumber 所在行号
    * @param vo 输出VO,可空
    * @param variables 变量池，用于在一次导入过程中保存临时信息
    * @param allowOverrides 允许覆盖
    * @return 导入行对象
    */
protected ImportRow<InventoryPlacementRImportPo> excelToImportRow(InventoryPlacementRImportPo row, Integer rowNumber, InventoryPlacementRVo vo, Map<String, Object> variables, boolean allowOverrides) {
ImportRow<InventoryPlacementRImportPo> ir = new ImportRow<InventoryPlacementRImportPo>();
ir.setRowNumber(rowNumber);
ir.setRow(row);
if (SheetUtils.isAllFieldsEmpty(row)) {
ir.setEmptyRow(true);
ir.addError("", "空行");
return ir;
}
final Set<ConstraintViolation<InventoryPlacementRImportPo>> validate = validator.validate(row);
boolean isOutputVo = vo != null;
if (!isOutputVo) {
vo = new InventoryPlacementRVo();
}
BeanUtils.copyProperties(row, vo);
// 收集基本校验错误
ir.initErrors(validate, InventoryPlacementRImportPo.class);

// 一般数据处理
vo.setShelfLife(SheetUtils.tryToLong(row.getShelfLife()));
vo.setWayQty(SheetUtils.tryToDecimal(row.getWayQty()));
vo.setLockQty(SheetUtils.tryToDecimal(row.getLockQty()));
vo.setVersion(SheetUtils.tryToLong(row.getVersion()));
vo.setAuxQty(SheetUtils.tryToDecimal(row.getAuxQty()));
vo.setAuxId(SheetUtils.tryToLong(row.getAuxId()));
vo.setStockAmount(SheetUtils.tryToDecimal(row.getStockAmount()));
vo.setStockQty(SheetUtils.tryToDecimal(row.getStockQty()));
vo.setExpiryDate(SheetUtils.tryToDateTime(row.getExpiryDate()));
vo.setProductionDate(SheetUtils.tryToDate(row.getProductionDate()));
SheetUtils.datePreviewFormat(row.getProductionDate(), row::setProductionDate);
vo.setProjectId(SheetUtils.tryToLong(row.getProjectId()));
vo.setBinId(SheetUtils.tryToLong(row.getBinId()));
vo.setStockId(SheetUtils.tryToLong(row.getStockId()));
// 处理关关联字段“店铺”
if (!SheetUtils.isBlank(row.getShopIdShopName())  && SheetUtils.isBlank(row.getShopId())) {
ShopInfoSearchParamPo shopIdSearchParam = new ShopInfoSearchParamPo();
shopIdSearchParam.setLimit(2); // 最大只返回二行记录
shopIdSearchParam.setShopNameEq(SheetUtils.trim(row.getShopIdShopName()));
List<ShopInfoItemVo> shopIdMatches = shopInfoMapper.search(shopIdSearchParam);
if (shopIdMatches.size() == 0) {
                    ir.addError("shopIdShopName", "未能在店铺中找到匹配的数据");
                }
else if (shopIdMatches.size() > 1) {
                    ir.addError("shopIdShopName", "关联不唯一，在店铺中找到多个匹配的数据");
                }
else {
                            vo.setShopId(shopIdMatches.get(0).getId());
 }
}
// 处理关关联字段“物料内码”
if (!SheetUtils.isBlank(row.getItemIdName())  && SheetUtils.isBlank(row.getItemId())) {
ProductSearchParamPo itemIdSearchParam = new ProductSearchParamPo();
itemIdSearchParam.setLimit(2); // 最大只返回二行记录
itemIdSearchParam.setNameEq(SheetUtils.trim(row.getItemIdName()));
List<ProductItemVo> itemIdMatches = productMapper.search(itemIdSearchParam);
if (itemIdMatches.size() == 0) {
                    ir.addError("itemIdName", "未能在产品管理中找到匹配的数据");
                }
else if (itemIdMatches.size() > 1) {
                    ir.addError("itemIdName", "关联不唯一，在产品管理中找到多个匹配的数据");
                }
else {
                            vo.setItemId(itemIdMatches.get(0).getId());
//20230706 调整  
                                final Map apiParams = MapUtil.builder()
                                .put("id",itemIdMatches.get(0).getId())
                                .put("fromObjectName", "InventoryPlacementR")
                                .put("fromObjectId", vo.getId())
                                .put("fromField","itemId") 
                                .build();   
                                
final List<JoinModel> joinModels = CollUtil.newArrayList(
JoinModel.builder().sourceFieldName("sku").sourceType("field").sourceApi(null).targetFieldName("sku").rowFieldName("sku").apiParams(apiParams).required(false).importIgnore(false).build() ,
JoinModel.builder().sourceFieldName("picUrl").sourceType("field").sourceApi(null).targetFieldName("picUrl").rowFieldName(null).apiParams(apiParams).required(false).importIgnore(false).build() 
);
 SheetUtils.joinHandle(null, ir, null, vo, CollUtil.getFirst(itemIdMatches), joinModels); 
 }
}

return ir;
}
/**
    * 导入预览
    * @param sysFile 已上传到系统的文件
    * @param templateId 要使用的模版编号
    * @param allowOverrides 允许覆盖
    */
@Override
public List<ImportRow<InventoryPlacementRImportPo>> importPreview(SysFile sysFile, Long templateId, boolean allowOverrides) throws Exception {
    SysImportTemplate importTemplate = null;
        if (templateId != null && templateId > 0) {
          importTemplate = importTemplateService.getById(templateId);
          if (importTemplate == null) {
            throw new RException("指定的模版不可用！");
          }
        }
Map<String, Object> variables = new HashMap<>();
try (InputStream in = fileService.read(sysFile)) {
List<ImportRow<InventoryPlacementRImportPo>> rows = new ArrayList<>();

    // 读取 Excel 到 rows
    EasyExcel.read(in)
        .useDefaultListener(false)
        .registerReadListener(
new TemplateModelBuildEventListener<>(InventoryPlacementRImportPo.class, importTemplate) {
@Override
            public void invokeRow(InventoryPlacementRImportPo row, AnalysisContext cxt) {
              InventoryPlacementRVo vo = new InventoryPlacementRVo();
              ImportRow<InventoryPlacementRImportPo> ir = excelToImportRow(row, cxt.readRowHolder().getRowIndex(), vo, variables, allowOverrides);
              if (ir.isEmptyRow()) {
                return;
              }
if (allowOverrides) {
// FIXME: 未在平台上配置此对象《作为导入唯一识别》的《逻辑分组》配置，无法处理更新模式
}
rows.add(ir);
            }
        }
    ).sheet().doRead();
    
return rows;
}
}
/**
    * 完成导入
    * @param rows 预导入的行信息
    */
@Transactional(rollbackFor = Exception.class)
@Override
public void importDone(List<ImportRow<InventoryPlacementRImportPo>> rows, Long templateId) throws Exception {
    SysImportTemplate importTemplate = null;
        if (templateId != null && templateId > 0) {
          importTemplate = importTemplateService.getById(templateId);
          if (importTemplate == null) {
            throw new RException("指定的模版不可用！");
          }
        }
String sysImportBatchNo = StrUtil.concat(true, DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_MS_PATTERN), IdUtil.nanoId(4));
List<Long> importedIdList = new ArrayList<>();
Map<String, Object> variables = new HashMap<>();
for (int i = 0; i < rows.size(); i++) {
ImportRow<InventoryPlacementRImportPo> row = rows.get(i);
Long updateId = row.getUpdateId();
InventoryPlacementRVo vo = new InventoryPlacementRVo();
row = excelToImportRow(row.getRow(), row.getRowNumber(), vo, variables, true);
if (row.getErrors() != null && row.getErrors().size() > 0) {
throw new RException(
        String.format(
            "第%d行（Excel第%d行）数据验证错误：%s",
            i + 1,
            row.getRowNumber(),
            row.getJoinErrorMessage()
        )
    );
}
if (updateId != null) {
            vo.setId(updateId);
        }
SheetUtils.setSysImportTemplateName(vo,importTemplate.getTitle());
SheetUtils.setSysImportBatchNo(vo,sysImportBatchNo);
if (updateId == null) {
            importedIdList.add(create(vo));
        } else {
            update(vo);
            importedIdList.add(vo.getId());
        }
}
}
/**
    * 导出
    * @param templateId 使用模版（可以是 null 或 0 表示系统默认导出模版）
    * @param search 查询条件
    */
@Override
public void export(Long templateId, InventoryPlacementRSearchParamPo search, HttpServletResponse response) throws Exception {
if (templateId != null && templateId > 0) {
SysImportTemplate sysImportTemplate = importTemplateService.getById(templateId);
    if (sysImportTemplate == null) {
      throw new RException("所选的导出模版不可用");
    }
    CustomExcelExport.CustomExcelExportOptions exportOptions = new CustomExcelExport.CustomExcelExportOptions();
    exportOptions.setImportTemplate(sysImportTemplate);
    exportOptions.setSearchParam(search);
    exportOptions.setMainService(this);
exportOptions.addFieldMappingOption("shopId", new CustomExcelExport.FieldMappingOption()
            .setConverter((o, r) -> String.valueOf(((InventoryPlacementRItemVo)r).getShopIdShopName()))
    );
exportOptions.addFieldMappingOption("productionDate", new CustomExcelExport.FieldMappingOption()
                    .setConverter((o, r) -> DateUtils.toDateString((Date)o))
            );
exportOptions.addFieldMappingOption("itemId", new CustomExcelExport.FieldMappingOption()
            .setConverter((o, r) -> String.valueOf(((InventoryPlacementRItemVo)r).getItemIdName()))
    );
exportOptions.addStringFields("fnSku","shopIdShopName","batchNo","itemIdName","sku");
customExcelExport.run(exportOptions, response.getOutputStream());
return;
}
   search.setPageSize(EXPORT_QUERY_MAX_SIZE);
       int current = 1;
       List<InventoryPlacementRExcelPo> items = new ArrayList<InventoryPlacementRExcelPo>();
       while (true) {
          search.setCurrent(current);
          Page<InventoryPlacementRItemVo> page = this.pageSearch(search);
          if (page.getTotal() > EXPORT_MAX_SIZE) {
            throw new RException("导出记录数超出限制！");
          }
          List<InventoryPlacementRItemVo> list = page.getRecords();
          if (list.isEmpty()) {
            break;
          }
          for(InventoryPlacementRItemVo item : list) {
              InventoryPlacementRExcelPo excel = new InventoryPlacementRExcelPo();
              BeanUtils.copyProperties(item, excel);

              items.add(excel);
          }
          if (list.size() < EXPORT_QUERY_MAX_SIZE) {
            break;
          }
          current++;          
       } 

       String fileName = String.format("库存货位放置表(%s).xlsx", DateUtils.toString(null, DateUtils.PATTERN_DATE_TIME));
       ResponseUtils.setAttachmentFileName(response, fileName);
       EasyExcel.write(response.getOutputStream())
                .registerWriteHandler(new CustomExcelHandler())
                .sheet()
         .head(InventoryPlacementRExcelPo.class)
         .doWrite(items);
   }

/**
    * 通过编号查询库存货位放置表多主键(重复时返回最新的主键)
    */
@Override
public List<Long> getIdsById(List<Long> list) {
   return this.baseMapper.getIdsById(list);
}
/**
    * 通过库存货位放置表主键查询编号
    */
@Override
public Long getIdById(Long id) {
   return this.baseMapper.getIdById(id);
}
/**
    * 通过库存货位放置表主键查询编号列表
    */
@Override
public List<Long> getIdByIds(List<Long> ids) {
   List<Map<String, Object>> maps = this.baseMapper.getIdByIds(ids);
   // 将返回重新排列为输入相同顺序
   return ids.stream().map(id -> {
        Optional<Map<String, Object>> optionalMap = maps.stream().filter(map -> id.equals(map.get("id"))).findFirst();
        return optionalMap.map(stringObjectMap -> (Long) stringObjectMap.get("id")).orElse(null);
      }).collect(Collectors.toList());
}
/**
    * 获取详情
    * @param id 库存货位放置表的主键
    */
@Override
public InventoryPlacementRVo getVoById(Long id) throws Exception {
    InventoryPlacementRVo vo = this.baseMapper.selectVoById(id);
       if (vo == null) {
           return null;
       }
return vo;
}
/**
    * 分页查询库存货位放置表
    * @param search 查询条件
    * @return 库存货位放置表分页查询结果
    */
    @Override
   public Page<InventoryPlacementRItemVo> pageSearch(InventoryPlacementRSearchParamPo search) {
       Page<InventoryPlacementRItemVo> page = new Page<>();
if(Objects.nonNull(search.getWfStatus()) && StringUtils.hasText(search.getFormName())){
      List<WorkFlow>workFlowList = workFlowService.queryWfListByStatus(search.getWfStatus(),search.getFormName());
      if(CollectionUtils.isEmpty(workFlowList)){
        return page;
      }
      List<Long> ids = workFlowList.stream().map(WorkFlow::getRecordId).collect(Collectors.toList());
      if(CollectionUtils.isEmpty(ids)){
        return page;
      }
      search.setSelectedIds(ids);
    }
       search.initSort("t.id desc", "t.", SORT_FIELD_MAP);
       page = this.baseMapper.pageSearch(search.toPage(), search);
List<Long> recordIds = page.getRecords().stream().map(InventoryPlacementRItemVo::getId).collect(Collectors.toList());
    if (CollectionUtils.isEmpty(recordIds)) {
      return page;
    }
List<WorkFlow> workFlows = workFlowService.queryWfList(recordIds, Collections.singletonList(search.getFormName()));
    if (CollectionUtils.isEmpty(workFlows)) {
      return page;
    }
Map<Long, Integer> flowMap = workFlows.stream().collect(Collectors.toMap(WorkFlow::getRecordId, WorkFlow::getWfStatus));
page.getRecords().stream().forEach(item -> {
        if (!flowMap.containsKey(item.getId())) {
          return;
        }
        item.setWfStatus(flowMap.get(item.getId()));
      });
    return page;
   }
/**
    * 库存货位放置表快速查询选项(有缓存)
    * @param search 查询条件
    * @return 选项结果
    */
   @Override   
   @Cacheable(value = CacheConfig.FAST, key = "'InventoryPlacementROptions::' + @userInfo.userTypeAndId + '::' + #search.keyword")
   public List<OptionItem<InventoryPlacementRItemVo>> searchOptions(InventoryPlacementRSearchParamPo search) {
       search.initSort("t.id desc", "t.", SORT_FIELD_MAP);
       return this.baseMapper.searchOptions(search);
}

   /**
    * 列表查询库存货位放置表
    * @param search 查询条件
    * @return 库存货位放置表列表查询结果
    */
    @Override
   public List<InventoryPlacementRItemVo> search(InventoryPlacementRSearchParamPo search) {
       search.initSort("t.id desc", "t.", SORT_FIELD_MAP);
       return this.baseMapper.search(search);
   }

    /**
     * 查询库存货位放置表最后更新时间
     * @param search 查询条件
     * @return 库存货位放置表最后更新时间
     */
    @Override
    public Date searchLastUpdateTime(InventoryPlacementRSearchParamPo search) {
        return this.baseMapper.searchLastUpdateTime(search);
    }
    
}
